diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj b/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj index 4ebbaeec61..bea1675a90 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj @@ -12,11 +12,12 @@ Microsoft.AspNetCore.Mvc.IActionResult - - - - + + + + + diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs index 3af3e8d9b2..f28aa5fed6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs @@ -3,5 +3,35 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ApiExplorer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Cors, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.DataAnnotations, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Json, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Xml, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Localization, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.TagHelpers, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Testing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ViewFeatures, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] + [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Abstractions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ApiExplorer.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Cors.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.DataAnnotations.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Json.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Xml.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Localization.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.TagHelpers.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ViewFeatures.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Views.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] + [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj index 9ea5be90f4..5ccb12bef4 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj @@ -10,10 +10,6 @@ - - - - diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs index 7d63adcabf..7e047b1367 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs @@ -7,13 +7,13 @@ using System.Linq; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.EndpointConstraints; using Microsoft.Net.Http.Headers; +using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; namespace Microsoft.AspNetCore.Mvc { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs index b3d14d1be8..0f9a1cb6e4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs @@ -8,11 +8,11 @@ using System.Linq; using System.Threading; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; +using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; namespace Microsoft.AspNetCore.Mvc.Internal { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs index bf1f4d575d..9d307e7e23 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs @@ -6,13 +6,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Template; using Microsoft.AspNetCore.Routing.Tree; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Internal; +using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; namespace Microsoft.AspNetCore.Mvc.Internal { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs index 18ae818293..f343701440 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs @@ -10,9 +10,9 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Routing; +using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; namespace Microsoft.AspNetCore.Mvc.Internal { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs index f020c86bf3..767ed08505 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs @@ -7,11 +7,11 @@ using System.Diagnostics; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; +using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; namespace Microsoft.AspNetCore.Mvc.Internal { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.cs index 93f7277522..c40318e2d9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.cs @@ -5,10 +5,10 @@ using System; using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; +using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; namespace Microsoft.AspNetCore.Mvc.Internal { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 786ce62b61..2d181e9754 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -293,6 +293,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal return invoker.InvokeAsync(); }; + var metadataCollection = BuildEndpointMetadata(action, routeName, source); + var endpoint = new MatcherEndpoint( + next => invokerDelegate, + template, + new RouteValueDictionary(nonInlineDefaults), + new RouteValueDictionary(action.RouteValues), + order, + metadataCollection, + action.DisplayName); + + // Use defaults after the endpoint is created as it merges both the inline and + // non-inline defaults into one. + EnsureRequiredValuesInDefaults(endpoint.RequiredValues, endpoint.Defaults); + + return endpoint; + } + + private static EndpointMetadataCollection BuildEndpointMetadata(ActionDescriptor action, string routeName, object source) + { var metadata = new List(); // REVIEW: Used for debugging. Consider removing before release metadata.Add(source); @@ -312,30 +331,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (action.ActionConstraints != null && action.ActionConstraints.Count > 0) { + // REVIEW: What is the best way to pick up endpoint constraints of an ActionDescriptor? + // Currently they need to implement IActionConstraintMetadata foreach (var actionConstraint in action.ActionConstraints) { if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint) { metadata.Add(new HttpMethodEndpointConstraint(httpMethodActionConstraint.HttpMethods)); } + else if (actionConstraint is IEndpointConstraintMetadata) + { + // The constraint might have been added earlier, e.g. it is also a filter descriptor + if (!metadata.Contains(actionConstraint)) + { + metadata.Add(actionConstraint); + } + } } } var metadataCollection = new EndpointMetadataCollection(metadata); - var endpoint = new MatcherEndpoint( - next => invokerDelegate, - template, - new RouteValueDictionary(nonInlineDefaults), - new RouteValueDictionary(action.RouteValues), - order, - metadataCollection, - action.DisplayName); - - // Use defaults after the endpoint is created as it merges both the inline and - // non-inline defaults into one. - EnsureRequiredValuesInDefaults(endpoint.RequiredValues, endpoint.Defaults); - - return endpoint; + return metadataCollection; } // Ensure required values are a subset of defaults diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj b/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj index 8014ae9cc2..7fc4891804 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj @@ -24,24 +24,27 @@ Microsoft.AspNetCore.Mvc.RouteAttribute - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs index 964f733284..31781090a1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs @@ -4,8 +4,36 @@ using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Mvc.Formatters; -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: TypeForwardedTo(typeof(InputFormatterException))] + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ApiExplorer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Cors, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.DataAnnotations, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Json, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Xml, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Localization, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.TagHelpers, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Testing, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ViewFeatures, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Abstractions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ApiExplorer.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Cors.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.DataAnnotations.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Json.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Xml.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Localization.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.TagHelpers.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ViewFeatures.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Views.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] + [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -[assembly: TypeForwardedTo(typeof(InputFormatterException))] diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj index 336474aae0..776a31be2d 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj @@ -11,8 +11,6 @@ - - diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj index b716f5cbbb..6458ed6838 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj @@ -10,8 +10,5 @@ - - - diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj b/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj index ea50d25312..2105530c41 100644 --- a/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj @@ -17,7 +17,6 @@ Microsoft.AspNetCore.Mvc.Localization.IViewLocalizer - diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs new file mode 100644 index 0000000000..7485a15694 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs @@ -0,0 +1,9 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + internal interface IModelTypeProvider + { + Type GetModelType(); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj b/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj index f08f8465c9..decdf16adf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj @@ -19,10 +19,6 @@ - - - - diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs index 831fbe57e1..5bd324d5d6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs @@ -3,5 +3,7 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] + [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.cs index 758255f494..cc9a20c8cb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Mvc.Razor { @@ -19,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor { // Name of the "public TModel Model" property on RazorPage private const string ModelPropertyName = "Model"; - private readonly ConcurrentDictionary _activationInfo; + private readonly ConcurrentDictionary _activationInfo; private readonly IModelMetadataProvider _metadataProvider; // Value accessors for common singleton properties activated in a RazorPage. @@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor HtmlEncoder htmlEncoder, IModelExpressionProvider modelExpressionProvider) { - _activationInfo = new ConcurrentDictionary(); + _activationInfo = new ConcurrentDictionary(); _metadataProvider = metadataProvider; _propertyAccessors = new RazorPagePropertyActivator.PropertyValueAccessors @@ -62,26 +63,76 @@ namespace Microsoft.AspNetCore.Mvc.Razor throw new ArgumentNullException(nameof(context)); } + var propertyActivator = GetOrAddCacheEntry(page); + propertyActivator.Activate(page, context); + } + + internal RazorPagePropertyActivator GetOrAddCacheEntry(IRazorPage page) + { var pageType = page.GetType(); - RazorPagePropertyActivator propertyActivator; - if (!_activationInfo.TryGetValue(pageType, out propertyActivator)) + Type providedModelType = null; + if (page is IModelTypeProvider modelTypeProvider) + { + providedModelType = modelTypeProvider.GetModelType(); + } + + // We only need to vary by providedModelType since it varies at runtime. Defined model type + // is synonymous with the pageType and consequently does not need to be accounted for in the cache key. + var cacheKey = new CacheKey(pageType, providedModelType); + if (!_activationInfo.TryGetValue(cacheKey, out var propertyActivator)) { // Look for a property named "Model". If it is non-null, we'll assume this is // the equivalent of TModel Model property on RazorPage. // // Otherwise if we don't have a model property the activator will just skip setting // the view data. - var modelType = pageType.GetRuntimeProperty(ModelPropertyName)?.PropertyType; + var modelType = providedModelType; + if (modelType == null) + { + modelType = pageType.GetRuntimeProperty(ModelPropertyName)?.PropertyType; + } + propertyActivator = new RazorPagePropertyActivator( pageType, modelType, _metadataProvider, _propertyAccessors); - propertyActivator = _activationInfo.GetOrAdd(pageType, propertyActivator); + propertyActivator = _activationInfo.GetOrAdd(cacheKey, propertyActivator); } - propertyActivator.Activate(page, context); + return propertyActivator; + } + + private readonly struct CacheKey : IEquatable + { + public CacheKey(Type pageType, Type providedModelType) + { + PageType = pageType; + ProvidedModelType = providedModelType; + } + + public Type PageType { get; } + + public Type ProvidedModelType { get; } + + public bool Equals(CacheKey other) + { + return PageType == other.PageType && + ProvidedModelType == other.ProvidedModelType; + } + + public override int GetHashCode() + { + var hashCodeCombiner = HashCodeCombiner.Start(); + hashCodeCombiner.Add(PageType); + if (ProvidedModelType != null) + { + hashCodeCombiner.Add(ProvidedModelType); + } + + return hashCodeCombiner.CombinedHash; + } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs index 240c80681c..10ef8139da 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs @@ -96,6 +96,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// public IReadOnlyList ViewStartPages { get; } + internal Action OnAfterPageActivated { get; set; } + /// public virtual async Task RenderAsync(ViewContext context) { @@ -167,6 +169,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor page.ViewContext = context; _pageActivator.Activate(page, context); + OnAfterPageActivated?.Invoke(page, context); + _diagnosticSource.BeforeViewPage(page, context); try diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs index 6340d87005..78a301947a 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs @@ -5,6 +5,7 @@ using System; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages; +using Resources = Microsoft.AspNetCore.Mvc.RazorPages.Resources; namespace Microsoft.Extensions.DependencyInjection { diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs index 35a6e75a45..2f17cfbbca 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Internal; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using Resources = Microsoft.AspNetCore.Mvc.RazorPages.Resources; namespace Microsoft.Extensions.DependencyInjection { diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs index 10539c0b2a..967c6fa07d 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs @@ -76,13 +76,29 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure } var viewContext = result.Page.ViewContext; + var pageAdapter = new RazorPageAdapter(result.Page, pageContext.ActionDescriptor.DeclaredModelTypeInfo); + viewContext.View = new RazorView( _razorViewEngine, _razorPageActivator, viewStarts, - new RazorPageAdapter(result.Page), + pageAdapter, _htmlEncoder, - _diagnosticSource); + _diagnosticSource) + { + OnAfterPageActivated = (page, currentViewContext) => + { + if (page != pageAdapter) + { + return; + } + + // ViewContext is always activated with the "right" ViewData type. + // Copy that over to the PageContext since PageContext.ViewData is exposed + // as the ViewData property on the Page that the user works with. + pageContext.ViewData = currentViewContext.ViewData; + }, + }; return ExecuteAsync(viewContext, result.ContentType, result.StatusCode); } diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs index 452161083b..52c319f22a 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs @@ -14,10 +14,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure // // The page gets activated before handler methods run, but the RazorView will also activate // each page. - public class RazorPageAdapter : IRazorPage + public class RazorPageAdapter : IRazorPage, IModelTypeProvider { private readonly RazorPageBase _page; + private readonly Type _modelType; + [Obsolete("This constructor is obsolete and will be removed in a future version.")] public RazorPageAdapter(RazorPageBase page) { if (page == null) @@ -28,6 +30,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure _page = page; } + public RazorPageAdapter(RazorPageBase page, Type modelType) + { + _page = page ?? throw new ArgumentNullException(nameof(page)); + _modelType = modelType ?? throw new ArgumentNullException(nameof(modelType)); + } + public ViewContext ViewContext { get { return _page.ViewContext; } @@ -75,5 +83,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { return _page.ExecuteAsync(); } + + Type IModelTypeProvider.GetModelType() => _modelType; } } diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj b/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj index f0be433ea5..ff5c61f35b 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj @@ -10,16 +10,6 @@ - - - - - - - - - - diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Microsoft.AspNetCore.Mvc.TagHelpers.csproj b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Microsoft.AspNetCore.Mvc.TagHelpers.csproj index 75d40d73c2..23fce08ef7 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Microsoft.AspNetCore.Mvc.TagHelpers.csproj +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Microsoft.AspNetCore.Mvc.TagHelpers.csproj @@ -19,8 +19,6 @@ - - diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj index 27d839320d..79ef166f49 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj @@ -19,16 +19,8 @@ Microsoft.AspNetCore.Mvc.ViewComponent - - - - - - - - diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestResources.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestResources.cs index 4d02719fa8..86f52aacbd 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestResources.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestResources.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Globalization; -using Microsoft.AspNetCore.Mvc.DataAnnotations.Test; +using Resources = Microsoft.AspNetCore.Mvc.DataAnnotations.Test.Resources; namespace Microsoft.AspNetCore.Mvc.ModelBinding { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs index 6d43c53c97..0a645b9e03 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs @@ -607,6 +607,22 @@ Hello from /Pages/Shared/"; Assert.Equal(HttpStatusCode.OK, response.StatusCode); } + [Fact] + public async Task ViewDataSetInViewStart_IsAvailableToPage() + { + // Arrange & Act + var document = await Client.GetHtmlDocumentAsync("/ViewData/ViewDataSetInViewStart"); + + // Assert + var valueSetInViewStart = document.RequiredQuerySelector("#valuefromviewstart").TextContent; + var valueSetInPageModel = document.RequiredQuerySelector("#valuefrompagemodel").TextContent; + var valueSetInPage = document.RequiredQuerySelector("#valuefrompage").TextContent; + + Assert.Equal("Value from _ViewStart", valueSetInViewStart); + Assert.Equal("Value from Page Model", valueSetInPageModel); + Assert.Equal("Value from Page", valueSetInPage); + } + private async Task AddAntiforgeryHeadersAsync(HttpRequestMessage request) { var response = await Client.GetAsync(request.RequestUri); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningDispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningDispatchingTests.cs new file mode 100644 index 0000000000..ff0c8e700c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningDispatchingTests.cs @@ -0,0 +1,13 @@ +// 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. + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class VersioningDispatchingTests : VersioningTestsBase + { + public VersioningDispatchingTests(MvcTestFixture fixture) + : base(fixture) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs index 02a86d0871..8490fda117 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs @@ -1,562 +1,13 @@ // 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 System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Xunit; - namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class VersioningTests : IClassFixture> + public class VersioningTests : VersioningTestsBase { public VersioningTests(MvcTestFixture fixture) + : base(fixture) { - Client = fixture.CreateDefaultClient(); - } - - public HttpClient Client { get; } - - [Theory] - [InlineData("1")] - [InlineData("2")] - public async Task AttributeRoutedAction_WithVersionedRoutes_IsNotAmbiguous(string version) - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/Addresses?version=" + version); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("api/addresses", result.ExpectedUrls); - Assert.Equal("Address", result.Controller); - Assert.Equal("GetV" + version, result.Action); - } - - [Theory] - [InlineData("1")] - [InlineData("2")] - public async Task AttributeRoutedAction_WithAmbiguousVersionedRoutes_CanBeDisambiguatedUsingOrder(string version) - { - // Arrange - var query = "?version=" + version; - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/Addresses/All" + query); - - // Act - - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/addresses/all?version=" + version, result.ExpectedUrls); - Assert.Equal("Address", result.Controller); - Assert.Equal("GetAllV" + version, result.Action); - } - - [Fact] - public async Task VersionedApi_CanReachV1Operations_OnTheSameController_WithNoVersionSpecified() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Tickets", result.Controller); - Assert.Equal("Get", result.Action); - - Assert.DoesNotContain("id", result.RouteValues.Keys); - } - - [Fact] - public async Task VersionedApi_CanReachV1Operations_OnTheSameController_WithVersionSpecified() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets?version=2"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Tickets", result.Controller); - Assert.Equal("Get", result.Action); - } - - [Fact] - public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheSameController() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets/5"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Tickets", result.Controller); - Assert.Equal("GetById", result.Action); - } - - [Fact] - public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheSameController_WithVersionSpecified() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets/5?version=2"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Tickets", result.Controller); - Assert.Equal("GetById", result.Action); - Assert.NotEmpty(result.RouteValues); - - Assert.Contains( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Theory] - [InlineData("2")] - [InlineData("3")] - [InlineData("4")] - public async Task VersionedApi_CanReachOtherVersionOperations_OnTheSameController(string version) - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Tickets?version=" + version); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Tickets", result.Controller); - Assert.Equal("Post", result.Action); - Assert.NotEmpty(result.RouteValues); - - Assert.DoesNotContain( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Fact] - public async Task VersionedApi_CanNotReachOtherVersionOperations_OnTheSameController_WithNoVersionSpecified() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Tickets"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - - var body = await response.Content.ReadAsByteArrayAsync(); - Assert.Empty(body); - } - - [Theory] - [InlineData("PUT", "Put", "2")] - [InlineData("PUT", "Put", "3")] - [InlineData("PUT", "Put", "4")] - [InlineData("DELETE", "Delete", "2")] - [InlineData("DELETE", "Delete", "3")] - [InlineData("DELETE", "Delete", "4")] - public async Task VersionedApi_CanReachOtherVersionOperationsWithParameters_OnTheSameController( - string method, - string action, - string version) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Tickets/5?version=" + version); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Tickets", result.Controller); - Assert.Equal(action, result.Action); - Assert.NotEmpty(result.RouteValues); - - Assert.Contains( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Theory] - [InlineData("PUT")] - [InlineData("DELETE")] - public async Task VersionedApi_CanNotReachOtherVersionOperationsWithParameters_OnTheSameController_WithNoVersionSpecified(string method) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Tickets/5"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - - var body = await response.Content.ReadAsByteArrayAsync(); - Assert.Empty(body); - } - - [Theory] - [InlineData("3")] - [InlineData("4")] - [InlineData("5")] - public async Task VersionedApi_CanUseOrderToDisambiguate_OverlappingVersionRanges(string version) - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Books?version=" + version); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Books", result.Controller); - Assert.Equal("GetBreakingChange", result.Action); - } - - [Theory] - [InlineData("1")] - [InlineData("2")] - [InlineData("6")] - public async Task VersionedApi_OverlappingVersionRanges_FallsBackToLowerOrderAction(string version) - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Books?version=" + version); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Books", result.Controller); - Assert.Equal("Get", result.Action); - } - - - [Theory] - [InlineData("GET", "Get")] - [InlineData("POST", "Post")] - public async Task VersionedApi_CanReachV1Operations_OnTheOriginalController_WithNoVersionSpecified(string method, string action) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Movies", result.Controller); - Assert.Equal(action, result.Action); - } - - [Theory] - [InlineData("GET", "Get")] - [InlineData("POST", "Post")] - public async Task VersionedApi_CanReachV1Operations_OnTheOriginalController_WithVersionSpecified(string method, string action) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies?version=2"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Movies", result.Controller); - Assert.Equal(action, result.Action); - } - - [Theory] - [InlineData("GET", "GetById")] - [InlineData("PUT", "Put")] - [InlineData("DELETE", "Delete")] - public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheOriginalController(string method, string action) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies/5"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Movies", result.Controller); - Assert.Equal(action, result.Action); - } - - [Theory] - [InlineData("GET", "GetById")] - [InlineData("DELETE", "Delete")] - public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheOriginalController_WithVersionSpecified(string method, string action) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies/5?version=2"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Movies", result.Controller); - Assert.Equal(action, result.Action); - } - - [Fact] - public async Task VersionedApi_CanReachOtherVersionOperationsWithParameters_OnTheV2Controller() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Put, "http://localhost/Movies/5?version=2"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("MoviesV2", result.Controller); - Assert.Equal("Put", result.Action); - Assert.NotEmpty(result.RouteValues); - } - - [Theory] - [InlineData("v1/Pets")] - [InlineData("v2/Pets")] - public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnTheSameAction(string url) - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Pets", result.Controller); - Assert.Equal("Get", result.Action); - } - - [Theory] - [InlineData("v1/Pets/5", "V1")] - [InlineData("v2/Pets/5", "V2")] - public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnDifferentActions(string url, string version) - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Pets", result.Controller); - Assert.Equal("Get" + version, result.Action); - } - - [Theory] - [InlineData("v1/Pets", "V1")] - [InlineData("v2/Pets", "V2")] - public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnDifferentActions_WithInlineConstraint(string url, string version) - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + url); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Pets", result.Controller); - Assert.Equal("Post" + version, result.Action); - } - - [Theory] - [InlineData("Customers/5", "?version=1", "Get")] - [InlineData("Customers/5", "?version=2", "Get")] - [InlineData("Customers/5", "?version=3", "GetV3ToV5")] - [InlineData("Customers/5", "?version=4", "GetV3ToV5")] - [InlineData("Customers/5", "?version=5", "GetV3ToV5")] - public async Task VersionedApi_CanProvideVersioningInformation_UsingPlainActionConstraint(string url, string query, string actionName) - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url + query); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Customers", result.Controller); - Assert.Equal(actionName, result.Action); - } - - [Fact] - public async Task VersionedApi_ConstraintOrder_IsRespected() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + "Customers?version=2"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Customers", result.Controller); - Assert.Equal("AnyV2OrHigher", result.Action); - } - - [Fact] - public async Task VersionedApi_CanUseConstraintOrder_ToChangeSelectedAction() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Delete, "http://localhost/" + "Customers/5?version=2"); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Customers", result.Controller); - Assert.Equal("Delete", result.Action); - } - - [Theory] - [InlineData("1")] - [InlineData("2")] - public async Task VersionedApi_MultipleVersionsUsingAttributeRouting_OnTheSameMethod(string version) - { - // Arrange - var path = "/" + version + "/Vouchers?version=" + version; - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost" + path); - - // Act - var response = await Client.SendAsync(message); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Vouchers", result.Controller); - Assert.Equal("GetVouchersMultipleVersions", result.Action); - - var actualUrl = Assert.Single(result.ExpectedUrls); - Assert.Equal(path, actualUrl); - } - - private class RoutingResult - { - public string[] ExpectedUrls { get; set; } - - public string ActualUrl { get; set; } - - public Dictionary RouteValues { get; set; } - - public string RouteName { get; set; } - - public string Action { get; set; } - - public string Controller { get; set; } - - public string Link { get; set; } } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs new file mode 100644 index 0000000000..46cfdbe4a8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs @@ -0,0 +1,568 @@ +// 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 System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public abstract class VersioningTestsBase : IClassFixture> where TStartup : class + { + protected VersioningTestsBase(MvcTestFixture fixture) + { + var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder); + Client = factory.CreateDefaultClient(); + } + + private static void ConfigureWebHostBuilder(IWebHostBuilder builder) => + builder.UseStartup(); + + public HttpClient Client { get; } + + [Theory] + [InlineData("1")] + [InlineData("2")] + public async Task AttributeRoutedAction_WithVersionedRoutes_IsNotAmbiguous(string version) + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/Addresses?version=" + version); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("api/addresses", result.ExpectedUrls); + Assert.Equal("Address", result.Controller); + Assert.Equal("GetV" + version, result.Action); + } + + [Theory] + [InlineData("1")] + [InlineData("2")] + public async Task AttributeRoutedAction_WithAmbiguousVersionedRoutes_CanBeDisambiguatedUsingOrder(string version) + { + // Arrange + var query = "?version=" + version; + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/Addresses/All" + query); + + // Act + + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/api/addresses/all?version=" + version, result.ExpectedUrls); + Assert.Equal("Address", result.Controller); + Assert.Equal("GetAllV" + version, result.Action); + } + + [Fact] + public async Task VersionedApi_CanReachV1Operations_OnTheSameController_WithNoVersionSpecified() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Tickets", result.Controller); + Assert.Equal("Get", result.Action); + + Assert.DoesNotContain("id", result.RouteValues.Keys); + } + + [Fact] + public async Task VersionedApi_CanReachV1Operations_OnTheSameController_WithVersionSpecified() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets?version=2"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Tickets", result.Controller); + Assert.Equal("Get", result.Action); + } + + [Fact] + public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheSameController() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets/5"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Tickets", result.Controller); + Assert.Equal("GetById", result.Action); + } + + [Fact] + public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheSameController_WithVersionSpecified() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets/5?version=2"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Tickets", result.Controller); + Assert.Equal("GetById", result.Action); + Assert.NotEmpty(result.RouteValues); + + Assert.Contains( + new KeyValuePair("id", "5"), + result.RouteValues); + } + + [Theory] + [InlineData("2")] + [InlineData("3")] + [InlineData("4")] + public async Task VersionedApi_CanReachOtherVersionOperations_OnTheSameController(string version) + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Tickets?version=" + version); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Tickets", result.Controller); + Assert.Equal("Post", result.Action); + Assert.NotEmpty(result.RouteValues); + + Assert.DoesNotContain( + new KeyValuePair("id", "5"), + result.RouteValues); + } + + [Fact] + public async Task VersionedApi_CanNotReachOtherVersionOperations_OnTheSameController_WithNoVersionSpecified() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Tickets"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + + var body = await response.Content.ReadAsByteArrayAsync(); + Assert.Empty(body); + } + + [Theory] + [InlineData("PUT", "Put", "2")] + [InlineData("PUT", "Put", "3")] + [InlineData("PUT", "Put", "4")] + [InlineData("DELETE", "Delete", "2")] + [InlineData("DELETE", "Delete", "3")] + [InlineData("DELETE", "Delete", "4")] + public async Task VersionedApi_CanReachOtherVersionOperationsWithParameters_OnTheSameController( + string method, + string action, + string version) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Tickets/5?version=" + version); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Tickets", result.Controller); + Assert.Equal(action, result.Action); + Assert.NotEmpty(result.RouteValues); + + Assert.Contains( + new KeyValuePair("id", "5"), + result.RouteValues); + } + + [Theory] + [InlineData("PUT")] + [InlineData("DELETE")] + public async Task VersionedApi_CanNotReachOtherVersionOperationsWithParameters_OnTheSameController_WithNoVersionSpecified(string method) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Tickets/5"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + + var body = await response.Content.ReadAsByteArrayAsync(); + Assert.Empty(body); + } + + [Theory] + [InlineData("3")] + [InlineData("4")] + [InlineData("5")] + public async Task VersionedApi_CanUseOrderToDisambiguate_OverlappingVersionRanges(string version) + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Books?version=" + version); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Books", result.Controller); + Assert.Equal("GetBreakingChange", result.Action); + } + + [Theory] + [InlineData("1")] + [InlineData("2")] + [InlineData("6")] + public async Task VersionedApi_OverlappingVersionRanges_FallsBackToLowerOrderAction(string version) + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Books?version=" + version); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Books", result.Controller); + Assert.Equal("Get", result.Action); + } + + + [Theory] + [InlineData("GET", "Get")] + [InlineData("POST", "Post")] + public async Task VersionedApi_CanReachV1Operations_OnTheOriginalController_WithNoVersionSpecified(string method, string action) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Movies", result.Controller); + Assert.Equal(action, result.Action); + } + + [Theory] + [InlineData("GET", "Get")] + [InlineData("POST", "Post")] + public async Task VersionedApi_CanReachV1Operations_OnTheOriginalController_WithVersionSpecified(string method, string action) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies?version=2"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Movies", result.Controller); + Assert.Equal(action, result.Action); + } + + [Theory] + [InlineData("GET", "GetById")] + [InlineData("PUT", "Put")] + [InlineData("DELETE", "Delete")] + public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheOriginalController(string method, string action) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies/5"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Movies", result.Controller); + Assert.Equal(action, result.Action); + } + + [Theory] + [InlineData("GET", "GetById")] + [InlineData("DELETE", "Delete")] + public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheOriginalController_WithVersionSpecified(string method, string action) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies/5?version=2"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Movies", result.Controller); + Assert.Equal(action, result.Action); + } + + [Fact] + public async Task VersionedApi_CanReachOtherVersionOperationsWithParameters_OnTheV2Controller() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Put, "http://localhost/Movies/5?version=2"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("MoviesV2", result.Controller); + Assert.Equal("Put", result.Action); + Assert.NotEmpty(result.RouteValues); + } + + [Theory] + [InlineData("v1/Pets")] + [InlineData("v2/Pets")] + public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnTheSameAction(string url) + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Pets", result.Controller); + Assert.Equal("Get", result.Action); + } + + [Theory] + [InlineData("v1/Pets/5", "V1")] + [InlineData("v2/Pets/5", "V2")] + public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnDifferentActions(string url, string version) + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Pets", result.Controller); + Assert.Equal("Get" + version, result.Action); + } + + [Theory] + [InlineData("v1/Pets", "V1")] + [InlineData("v2/Pets", "V2")] + public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnDifferentActions_WithInlineConstraint(string url, string version) + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + url); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Pets", result.Controller); + Assert.Equal("Post" + version, result.Action); + } + + [Theory] + [InlineData("Customers/5", "?version=1", "Get")] + [InlineData("Customers/5", "?version=2", "Get")] + [InlineData("Customers/5", "?version=3", "GetV3ToV5")] + [InlineData("Customers/5", "?version=4", "GetV3ToV5")] + [InlineData("Customers/5", "?version=5", "GetV3ToV5")] + public async Task VersionedApi_CanProvideVersioningInformation_UsingPlainActionConstraint(string url, string query, string actionName) + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url + query); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Customers", result.Controller); + Assert.Equal(actionName, result.Action); + } + + [Fact] + public async Task VersionedApi_ConstraintOrder_IsRespected() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + "Customers?version=2"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Customers", result.Controller); + Assert.Equal("AnyV2OrHigher", result.Action); + } + + [Fact] + public async Task VersionedApi_CanUseConstraintOrder_ToChangeSelectedAction() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Delete, "http://localhost/" + "Customers/5?version=2"); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Customers", result.Controller); + Assert.Equal("Delete", result.Action); + } + + [Theory] + [InlineData("1")] + [InlineData("2")] + public async Task VersionedApi_MultipleVersionsUsingAttributeRouting_OnTheSameMethod(string version) + { + // Arrange + var path = "/" + version + "/Vouchers?version=" + version; + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost" + path); + + // Act + var response = await Client.SendAsync(message); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Vouchers", result.Controller); + Assert.Equal("GetVouchersMultipleVersions", result.Action); + + var actualUrl = Assert.Single(result.ExpectedUrls); + Assert.Equal(path, actualUrl); + } + + private class RoutingResult + { + public string[] ExpectedUrls { get; set; } + + public string ActualUrl { get; set; } + + public Dictionary RouteValues { get; set; } + + public string RouteName { get; set; } + + public string Action { get; set; } + + public string Controller { get; set; } + + public string Link { get; set; } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageActivatorTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageActivatorTest.cs index 17b0999b58..34a8b0926b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageActivatorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageActivatorTest.cs @@ -165,6 +165,69 @@ namespace Microsoft.AspNetCore.Mvc.Razor Assert.Throws(() => activator.Activate(instance, viewContext)); } + [Fact] + public void Activate_UsesModelFromModelTypeProvider() + { + // Arrange + var activator = CreateActivator(); + + var viewData = new ViewDataDictionary(MetadataProvider, new ModelStateDictionary()) + { + { "key", "value" }, + }; + var viewContext = CreateViewContext(viewData); + var page = new ModelTypeProviderRazorPage(); + + // Act + activator.Activate(page, viewContext); + + // Assert + Assert.Same(viewContext.ViewData, page.ViewData); + Assert.NotSame(viewData, viewContext.ViewData); + + Assert.IsType>(viewContext.ViewData); + Assert.Equal("value", viewContext.ViewData["key"]); + } + + [Fact] + public void GetOrAddCacheEntry_CachesPages() + { + // Arrange + var activator = CreateActivator(); + var page = new TestRazorPage(); + + // Act + var result1 = activator.GetOrAddCacheEntry(page); + var result2 = activator.GetOrAddCacheEntry(page); + + // Assert + Assert.Same(result1, result2); + } + + [Fact] + public void GetOrAddCacheEntry_VariesByModelType_IfPageIsModelTypeProvider() + { + // Arrange + var activator = CreateActivator(); + var page = new ModelTypeProviderRazorPage(); + + // Act - 1 + var result1 = activator.GetOrAddCacheEntry(page); + var result2 = activator.GetOrAddCacheEntry(page); + + // Assert - 1 + Assert.Same(result1, result2); + + // Act - 2 + page.ModelType = typeof(string); + var result3 = activator.GetOrAddCacheEntry(page); + var result4 = activator.GetOrAddCacheEntry(page); + + // Assert - 2 + Assert.Same(result3, result4); + Assert.NotSame(result1, result3); + } + private RazorPageActivator CreateActivator() { return new RazorPageActivator(MetadataProvider, UrlHelperFactory, JsonHelper, DiagnosticSource, HtmlEncoder, ModelExpressionProvider); @@ -225,6 +288,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor } } + private class ModelTypeProviderRazorPage : RazorPage, IModelTypeProvider + { + [RazorInject] + public ViewDataDictionary ViewData { get; set; } + + public Type ModelType { get; set; } = typeof(Guid); + + public override Task ExecuteAsync() + { + throw new NotImplementedException(); + } + + public Type GetModelType() => ModelType; + } + private abstract class NoModelPropertyBase : RazorPage { [RazorInject] diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs index cd5e8835a2..71fa07b67e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs @@ -1747,6 +1747,49 @@ namespace Microsoft.AspNetCore.Mvc.Razor Assert.Equal(expected, viewContext.Writer.ToString()); } + [Fact] + public async Task RenderAsync_InvokesOnAfterPageActivated() + { + // Arrange + var viewStart = new TestableRazorPage(_ => { }); + var page = new TestableRazorPage(p => { p.Layout = LayoutPath; }); + var layout = new TestableRazorPage(p => { p.RenderBodyPublic(); }); + var expected = new HashSet(); + var onAfterPageActivatedCalled = 0; + + var activated = new HashSet(); + var pageActivator = new Mock(); + pageActivator.Setup(p => p.Activate(It.IsAny(), It.IsAny())) + .Callback((IRazorPage p, ViewContext v) => activated.Add(p)); + + var viewEngine = new Mock(); + viewEngine.Setup(v => v.FindPage(It.IsAny(), LayoutPath)) + .Returns(new RazorPageResult(LayoutPath, layout)); + + var view = new RazorView( + viewEngine.Object, + pageActivator.Object, + new[] { viewStart }, + page, + new HtmlTestEncoder(), + new DiagnosticListener("Microsoft.AspNetCore.Mvc.Razor")) + { + OnAfterPageActivated = AssertActivated, + }; + var viewContext = CreateViewContext(view); + + // Act + await view.RenderAsync(viewContext); + Assert.Equal(3, onAfterPageActivatedCalled); + + void AssertActivated(IRazorPage p, ViewContext v) + { + onAfterPageActivatedCalled++; + expected.Add(p); + Assert.Equal(expected, activated); + } + } + private static ViewContext CreateViewContext(RazorView view) { var httpContext = new DefaultHttpContext(); diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TestResources.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TestResources.cs index 5c5b711090..5f34f8406c 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TestResources.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TestResources.cs @@ -1,7 +1,7 @@ // 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 Microsoft.AspNetCore.Mvc.ViewFeatures.Test; +using Resources = Microsoft.AspNetCore.Mvc.ViewFeatures.Test.Resources; namespace Microsoft.AspNetCore.Mvc { diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cs b/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cs new file mode 100644 index 0000000000..f911dbfe5e --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace RazorPagesWebSite.ViewDataSetInViewStart +{ + public class Index : PageModel + { + [ViewData] + public string ValueFromPageModel => "Value from Page Model"; + } +} diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cshtml b/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cshtml new file mode 100644 index 0000000000..683a1631e8 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cshtml @@ -0,0 +1,7 @@ +@page +@namespace RazorPagesWebSite.ViewDataSetInViewStart +@model Index +@{ + ViewData["ValueFromPage"] = "Value from Page"; +} +Sample that shows ViewData attributes being set in a PageModel. diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_Layout.cshtml b/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_Layout.cshtml new file mode 100644 index 0000000000..6b3bb02e51 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_Layout.cshtml @@ -0,0 +1,4 @@ +@RenderBody() +@ViewData["ValueFromViewStart"] +@ViewData["ValueFromPage"] +@ViewData["ValueFromPageModel"] diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_ViewStart.cshtml b/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_ViewStart.cshtml new file mode 100644 index 0000000000..51245c5fa0 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_ViewStart.cshtml @@ -0,0 +1,4 @@ +@{ + Layout = "_Layout"; + ViewData["ValueFromViewStart"] = "Value from _ViewStart"; +} \ No newline at end of file diff --git a/test/WebSites/VersioningWebSite/Program.cs b/test/WebSites/VersioningWebSite/Program.cs new file mode 100644 index 0000000000..7dce10d19d --- /dev/null +++ b/test/WebSites/VersioningWebSite/Program.cs @@ -0,0 +1,26 @@ +// 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.IO; +using Microsoft.AspNetCore.Hosting; + +namespace VersioningWebSite +{ + public class Program + { + public static void Main(string[] args) + { + var host = CreateWebHostBuilder(args) + .Build(); + + host.Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + new WebHostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .UseKestrel() + .UseIISIntegration(); + } +} diff --git a/test/WebSites/VersioningWebSite/Startup.cs b/test/WebSites/VersioningWebSite/Startup.cs index cdf090a0a6..5373436c06 100644 --- a/test/WebSites/VersioningWebSite/Startup.cs +++ b/test/WebSites/VersioningWebSite/Startup.cs @@ -24,21 +24,5 @@ namespace VersioningWebSite { app.UseMvcWithDefaultRoute(); } - - public static void Main(string[] args) - { - var host = CreateWebHostBuilder(args) - .Build(); - - host.Run(); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - new WebHostBuilder() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .UseKestrel() - .UseIISIntegration(); } -} - +} \ No newline at end of file diff --git a/test/WebSites/VersioningWebSite/StartupWithDispatching.cs b/test/WebSites/VersioningWebSite/StartupWithDispatching.cs new file mode 100644 index 0000000000..164f86550b --- /dev/null +++ b/test/WebSites/VersioningWebSite/StartupWithDispatching.cs @@ -0,0 +1,37 @@ +// 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.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + +namespace VersioningWebSite +{ + public class StartupWithDispatching + { + public void ConfigureServices(IServiceCollection services) + { + services.AddDispatcher(); + + // Add MVC services to the services container + services.AddMvc(); + + services.AddScoped(); + services.AddSingleton(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseDispatcher(); + + app.UseMvcWithEndpoint(endpoints => + { + endpoints.MapEndpoint( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + } +} \ No newline at end of file diff --git a/test/WebSites/VersioningWebSite/VersionAttribute.cs b/test/WebSites/VersioningWebSite/VersionAttribute.cs index e1658fd8b6..827aaef23c 100644 --- a/test/WebSites/VersioningWebSite/VersionAttribute.cs +++ b/test/WebSites/VersioningWebSite/VersionAttribute.cs @@ -3,10 +3,11 @@ using System; using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Routing.EndpointConstraints; namespace VersioningWebSite { - public class VersionAttribute : Attribute, IActionConstraintFactory + public class VersionAttribute : Attribute, IActionConstraintFactory, IEndpointConstraintFactory { private int? _maxVersion; private int? _minVersion; @@ -32,7 +33,12 @@ namespace VersioningWebSite public bool IsReusable => true; - public IActionConstraint CreateInstance(IServiceProvider services) + IActionConstraint IActionConstraintFactory.CreateInstance(IServiceProvider services) + { + return new VersionRangeValidator(_minVersion, _maxVersion) { Order = _order ?? 0 }; + } + + IEndpointConstraint IEndpointConstraintFactory.CreateInstance(IServiceProvider services) { return new VersionRangeValidator(_minVersion, _maxVersion) { Order = _order ?? 0 }; } diff --git a/test/WebSites/VersioningWebSite/VersionDeleteAttribute.cs b/test/WebSites/VersioningWebSite/VersionDeleteAttribute.cs index 822e281547..9354c56b23 100644 --- a/test/WebSites/VersioningWebSite/VersionDeleteAttribute.cs +++ b/test/WebSites/VersioningWebSite/VersionDeleteAttribute.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Routing; namespace VersioningWebSite { - public class VersionDeleteAttribute : VersionRoute, IActionHttpMethodProvider + public class VersionDeleteAttribute : VersionRouteAttribute, IActionHttpMethodProvider { public VersionDeleteAttribute(string template) : base(template) diff --git a/test/WebSites/VersioningWebSite/VersionGetAttribute.cs b/test/WebSites/VersioningWebSite/VersionGetAttribute.cs index 4e53537130..b809c585f9 100644 --- a/test/WebSites/VersioningWebSite/VersionGetAttribute.cs +++ b/test/WebSites/VersioningWebSite/VersionGetAttribute.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Routing; namespace VersioningWebSite { - public class VersionGetAttribute : VersionRoute, IActionHttpMethodProvider + public class VersionGetAttribute : VersionRouteAttribute, IActionHttpMethodProvider { public VersionGetAttribute(string template) : base(template) diff --git a/test/WebSites/VersioningWebSite/VersionPostAttribute.cs b/test/WebSites/VersioningWebSite/VersionPostAttribute.cs index 18db5eb210..9738cd946d 100644 --- a/test/WebSites/VersioningWebSite/VersionPostAttribute.cs +++ b/test/WebSites/VersioningWebSite/VersionPostAttribute.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Routing; namespace VersioningWebSite { - public class VersionPostAttribute : VersionRoute, IActionHttpMethodProvider + public class VersionPostAttribute : VersionRouteAttribute, IActionHttpMethodProvider { public VersionPostAttribute(string template) : base(template) diff --git a/test/WebSites/VersioningWebSite/VersionPutAttribute.cs b/test/WebSites/VersioningWebSite/VersionPutAttribute.cs index 49d4dab380..ad1dafc176 100644 --- a/test/WebSites/VersioningWebSite/VersionPutAttribute.cs +++ b/test/WebSites/VersioningWebSite/VersionPutAttribute.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Routing; namespace VersioningWebSite { - public class VersionPutAttribute : VersionRoute, IActionHttpMethodProvider + public class VersionPutAttribute : VersionRouteAttribute, IActionHttpMethodProvider { public VersionPutAttribute(string template) : base(template) diff --git a/test/WebSites/VersioningWebSite/VersionRangeValidator.cs b/test/WebSites/VersioningWebSite/VersionRangeValidator.cs index 14c8d37662..ec8d244de3 100644 --- a/test/WebSites/VersioningWebSite/VersionRangeValidator.cs +++ b/test/WebSites/VersioningWebSite/VersionRangeValidator.cs @@ -3,10 +3,11 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Routing.EndpointConstraints; namespace VersioningWebSite { - public class VersionRangeValidator : IActionConstraint + public class VersionRangeValidator : IActionConstraint, IEndpointConstraint { private readonly int? _minVersion; private readonly int? _maxVersion; @@ -25,9 +26,19 @@ namespace VersioningWebSite } public bool Accept(ActionConstraintContext context) + { + return ProcessRequest(context.RouteContext.HttpContext.Request); + } + + public bool Accept(EndpointConstraintContext context) + { + return ProcessRequest(context.HttpContext.Request); + } + + private bool ProcessRequest(HttpRequest request) { int version; - if (int.TryParse(GetVersion(context.RouteContext.HttpContext.Request), out version)) + if (int.TryParse(GetVersion(request), out version)) { return (_minVersion == null || _minVersion <= version) && (_maxVersion == null || _maxVersion >= version); diff --git a/test/WebSites/VersioningWebSite/VersionRoute.cs b/test/WebSites/VersioningWebSite/VersionRouteAttribute.cs similarity index 84% rename from test/WebSites/VersioningWebSite/VersionRoute.cs rename to test/WebSites/VersioningWebSite/VersionRouteAttribute.cs index 3b6f61c429..288a488790 100644 --- a/test/WebSites/VersioningWebSite/VersionRoute.cs +++ b/test/WebSites/VersioningWebSite/VersionRouteAttribute.cs @@ -5,12 +5,13 @@ using System; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Routing.EndpointConstraints; namespace VersioningWebSite { - public class VersionRoute : RouteAttribute, IActionConstraintFactory + public class VersionRouteAttribute : RouteAttribute, IActionConstraintFactory, IEndpointConstraintFactory { - private readonly IActionConstraint _constraint; + private readonly IActionConstraint _actionConstraint; // 5 // [5] @@ -28,12 +29,12 @@ namespace VersioningWebSite public bool IsReusable => true; - public VersionRoute(string template) + public VersionRouteAttribute(string template) : base(template) { } - public VersionRoute(string template, string versionRange) + public VersionRouteAttribute(string template, string versionRange) : base(template) { var constraint = CreateVersionConstraint(versionRange); @@ -44,7 +45,7 @@ namespace VersioningWebSite throw new ArgumentException(message, "versionRange"); } - _constraint = constraint; + _actionConstraint = constraint; } private static IActionConstraint CreateVersionConstraint(string versionRange) @@ -122,9 +123,14 @@ namespace VersioningWebSite } } - public IActionConstraint CreateInstance(IServiceProvider services) + IActionConstraint IActionConstraintFactory.CreateInstance(IServiceProvider services) { - return _constraint; + return _actionConstraint; + } + + IEndpointConstraint IEndpointConstraintFactory.CreateInstance(IServiceProvider services) + { + return (IEndpointConstraint)_actionConstraint; } } } \ No newline at end of file